{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import os,random,shutil\n",
    "np.random.seed(7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1, 准备数据集，从“猫狗大战”数据集中选取1000Cat+1000Dog作为train set，选取400Cat+400Dog作为test set\n",
    "def ensure_folder_exists(folder_name): # 测试OK\n",
    "    '''\n",
    "    确保folder_name这个文件夹存在，如果不存在，则建立该文件夹，可以是多层嵌套文件夹\n",
    "    :param folder_name:\n",
    "    :return:\n",
    "    '''\n",
    "    if not os.path.exists(folder_name): os.makedirs(folder_name)\n",
    "\n",
    "def dataset_prepare(raw_set_folder,dst_folder,train_num_per_class=1000,test_num_per_class=400):\n",
    "    '''\n",
    "    准备小数据集，从原始的raw_set_folder数据集中提取train_num_per_class(每个类别)的照片放入train中，\n",
    "    提取val_num_per_class（每个类别）放入到validation文件夹中\n",
    "    :param raw_set_folder: 含有猫狗的照片，这些照片的名称必须为cat.101.jpg或dog.102.jpg形式\n",
    "    :param dst_folder: 将选取之后的图片放置到这个文件夹中\n",
    "    :param train_num_per_class:\n",
    "    :param test_num_per_class:\n",
    "    :return:\n",
    "    '''\n",
    "    all_imgs=glob(os.path.join(raw_set_folder,'*.jpg'))\n",
    "    img_len = len(all_imgs)\n",
    "    assert img_len > 0, '{} has no jpg image file'.format(raw_set_folder)\n",
    "\n",
    "    cat_imgs=[]\n",
    "    dog_imgs=[]\n",
    "    for img_path in all_imgs:\n",
    "        img_name=os.path.split(img_path)[1]\n",
    "        if img_name.startswith('cat'):\n",
    "            cat_imgs.append(img_path)\n",
    "        elif img_name.startswith('dog'):\n",
    "            dog_imgs.append(img_path)\n",
    "    random.shuffle(cat_imgs)\n",
    "    random.shuffle(dog_imgs)\n",
    "    [ensure_folder_exists(os.path.join(dst_folder,type_folder,class_folder)) for type_folder in ['train','test']\n",
    "        for class_folder in ['dogs','cats']]\n",
    "    # 下面的代码可以进一步优化。。。。\n",
    "    for cat_img_path in cat_imgs[:train_num_per_class]: # 最开始的N个图片作为train\n",
    "        _, fname = os.path.split(cat_img_path)  # 获取文件名和路径\n",
    "        shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'train', 'cats',fname))\n",
    "    print('imgs saved to train/cats folder')\n",
    "    for dog_img_path in dog_imgs[:train_num_per_class]:\n",
    "        _, fname = os.path.split(dog_img_path)  # 获取文件名和路径\n",
    "        shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'train', 'dogs',fname))\n",
    "    print('imgs saved to train/dogs folder')\n",
    "    for cat_img_path in cat_imgs[-test_num_per_class:]: # 最末的M个图片作为test\n",
    "        _, fname = os.path.split(cat_img_path)  # 获取文件名和路径\n",
    "        shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'test', 'cats',fname))\n",
    "    print('imgs saved to test/cats folder')\n",
    "    for dog_img_path in dog_imgs[-test_num_per_class:]: # 最末的M个图片作为test\n",
    "        _, fname = os.path.split(dog_img_path)  # 获取文件名和路径\n",
    "        shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'test', 'dogs',fname))\n",
    "    print('imgs saved to test/dogs folder')\n",
    "    print('finished...')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "imgs saved to train/cats folder\n",
      "imgs saved to train/dogs folder\n",
      "imgs saved to test/cats folder\n",
      "imgs saved to test/dogs folder\n",
      "finished...\n"
     ]
    }
   ],
   "source": [
    "dataset_prepare('E:\\PyProjects\\DataSet\\DogsVsCats/train','E:\\PyProjects\\DataSet\\DogsVsCats_Tiny',1000,400)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 为Keras模型准备数据集\n",
    "#1，指定一些超参数：\n",
    "train_data_dir='E:\\PyProjects\\DataSet\\DogsVsCats_Tiny/train'\n",
    "val_data_dir='E:\\PyProjects\\DataSet\\DogsVsCats_Tiny/test' # keras中将测试集称为validation set\n",
    "train_samples_num=2000 # train set中全部照片数\n",
    "val_samples_num=800\n",
    "IMG_W,IMG_H,IMG_CH=150,150,3 # 单张图片的大小\n",
    "batch_size=32\n",
    "epochs=20  # 用比较少的epochs数目做演示，节约训练时间"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 2000 images belonging to 2 classes.\n"
     ]
    }
   ],
   "source": [
    "# 2，准备训练集，keras有很多Generator可以直接处理图片的加载，增强等操作，封装的非常好\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "train_datagen = ImageDataGenerator( # 单张图片的处理方式，train时一般都会进行图片增强\n",
    "        rescale=1. / 255, # 图片像素值为0-255，此处都乘以1/255，调整到0-1之间\n",
    "        shear_range=0.2, # 斜切\n",
    "        zoom_range=0.2, # 放大缩小范围\n",
    "        horizontal_flip=True) # 水平翻转\n",
    "\n",
    "train_generator = train_datagen.flow_from_directory(# 从文件夹中产生数据流\n",
    "    train_data_dir, # 训练集图片的文件夹\n",
    "    target_size=(IMG_W, IMG_H), # 调整后每张图片的大小\n",
    "    batch_size=batch_size,\n",
    "    class_mode='binary') # 此处是二分类问题，故而mode是binary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 800 images belonging to 2 classes.\n"
     ]
    }
   ],
   "source": [
    "# 3，同样的方式准备测试集\n",
    "val_datagen = ImageDataGenerator(rescale=1. / 255) # 只需要和trainset同样的scale即可，不需增强\n",
    "val_generator = val_datagen.flow_from_directory(\n",
    "        val_data_dir,\n",
    "        target_size=(IMG_W, IMG_H),\n",
    "        batch_size=batch_size,\n",
    "        class_mode='binary')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 4，建立Keras模型：模型的建立主要包括模型的搭建，模型的配置\n",
    "from keras.models import Sequential\n",
    "from keras.layers import Conv2D, MaxPooling2D\n",
    "from keras.layers import Activation, Dropout, Flatten, Dense\n",
    "from keras import optimizers\n",
    "def build_model(input_shape):\n",
    "    # 模型的搭建：此处构建三个CNN层+2个全连接层的结构\n",
    "    model = Sequential()\n",
    "    model.add(Conv2D(32, (3, 3), input_shape=input_shape))\n",
    "    model.add(Activation('relu'))\n",
    "    model.add(MaxPooling2D(pool_size=(2, 2)))\n",
    "\n",
    "    model.add(Conv2D(32, (3, 3)))\n",
    "    model.add(Activation('relu'))\n",
    "    model.add(MaxPooling2D(pool_size=(2, 2)))\n",
    "\n",
    "    model.add(Conv2D(64, (3, 3)))\n",
    "    model.add(Activation('relu'))\n",
    "    model.add(MaxPooling2D(pool_size=(2, 2)))\n",
    "\n",
    "    model.add(Flatten())\n",
    "    model.add(Dense(64))\n",
    "    model.add(Activation('relu'))\n",
    "    model.add(Dropout(0.5)) # Dropout防止过拟合\n",
    "    model.add(Dense(1)) # 此处虽然是二分类，但是不能用Dense(2)，因为后面的activation是sigmoid，这个函数只能输出一个值，即class_0的概率\n",
    "    model.add(Activation('sigmoid')) #二分类问题用sigmoid作为activation function\n",
    "    \n",
    "    # 模型的配置\n",
    "    model.compile(loss='binary_crossentropy', # 定义模型的loss func，optimizer，\n",
    "                  optimizer=optimizers.RMSprop(lr=0.0001),\n",
    "                  metrics=['accuracy'])# 主要优化accuracy\n",
    "    # 二分类问题的loss function使用binary_crossentropy，此处使用准确率作为优化目标\n",
    "    return model # 返回构建好的模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20\n",
      "62/62 [==============================] - 136s 2s/step - loss: 0.6976 - acc: 0.5015 - val_loss: 0.6937 - val_acc: 0.5000\n",
      "Epoch 2/20\n",
      "62/62 [==============================] - 137s 2s/step - loss: 0.6926 - acc: 0.5131 - val_loss: 0.6846 - val_acc: 0.5813\n",
      "Epoch 3/20\n",
      "62/62 [==============================] - 152s 2s/step - loss: 0.6821 - acc: 0.5544 - val_loss: 0.6735 - val_acc: 0.6100\n",
      "Epoch 4/20\n",
      "62/62 [==============================] - 152s 2s/step - loss: 0.6779 - acc: 0.5700 - val_loss: 0.6656 - val_acc: 0.6075\n",
      "Epoch 5/20\n",
      "62/62 [==============================] - 154s 2s/step - loss: 0.6682 - acc: 0.5842 - val_loss: 0.6671 - val_acc: 0.5787\n",
      "Epoch 6/20\n",
      "62/62 [==============================] - 158s 3s/step - loss: 0.6609 - acc: 0.6053 - val_loss: 0.6419 - val_acc: 0.6525\n",
      "Epoch 7/20\n",
      "62/62 [==============================] - 150s 2s/step - loss: 0.6555 - acc: 0.6018 - val_loss: 0.6343 - val_acc: 0.6562\n",
      "Epoch 8/20\n",
      "62/62 [==============================] - 152s 2s/step - loss: 0.6417 - acc: 0.6144 - val_loss: 0.6210 - val_acc: 0.6750\n",
      "Epoch 9/20\n",
      "62/62 [==============================] - 145s 2s/step - loss: 0.6235 - acc: 0.6401 - val_loss: 0.6226 - val_acc: 0.6488\n",
      "Epoch 10/20\n",
      "62/62 [==============================] - 151s 2s/step - loss: 0.6289 - acc: 0.6421 - val_loss: 0.6167 - val_acc: 0.6687\n",
      "Epoch 11/20\n",
      "62/62 [==============================] - 158s 3s/step - loss: 0.6315 - acc: 0.6502 - val_loss: 0.6074 - val_acc: 0.6625\n",
      "Epoch 12/20\n",
      "62/62 [==============================] - 148s 2s/step - loss: 0.6131 - acc: 0.6638 - val_loss: 0.5980 - val_acc: 0.6900\n",
      "Epoch 13/20\n",
      "62/62 [==============================] - 141s 2s/step - loss: 0.6083 - acc: 0.6698 - val_loss: 0.6014 - val_acc: 0.6675\n",
      "Epoch 14/20\n",
      "62/62 [==============================] - 143s 2s/step - loss: 0.5977 - acc: 0.6769 - val_loss: 0.5938 - val_acc: 0.6763\n",
      "Epoch 15/20\n",
      "62/62 [==============================] - 143s 2s/step - loss: 0.5910 - acc: 0.6804 - val_loss: 0.5718 - val_acc: 0.7163\n",
      "Epoch 16/20\n",
      "62/62 [==============================] - 143s 2s/step - loss: 0.5934 - acc: 0.6774 - val_loss: 0.6087 - val_acc: 0.6538\n",
      "Epoch 17/20\n",
      "62/62 [==============================] - 139s 2s/step - loss: 0.5812 - acc: 0.6820 - val_loss: 0.5698 - val_acc: 0.7100\n",
      "Epoch 18/20\n",
      "62/62 [==============================] - 140s 2s/step - loss: 0.5776 - acc: 0.6880 - val_loss: 0.5615 - val_acc: 0.7262\n",
      "Epoch 19/20\n",
      "62/62 [==============================] - 143s 2s/step - loss: 0.5766 - acc: 0.6971 - val_loss: 0.5852 - val_acc: 0.6800\n",
      "Epoch 20/20\n",
      "62/62 [==============================] - 140s 2s/step - loss: 0.5654 - acc: 0.7117 - val_loss: 0.5374 - val_acc: 0.7450\n"
     ]
    }
   ],
   "source": [
    "model=build_model(input_shape=(IMG_W,IMG_H,IMG_CH)) # 输入的图片维度\n",
    "# 模型的训练\n",
    "history_ft = model.fit_generator(train_generator, # 数据流\n",
    "                        steps_per_epoch=train_samples_num // batch_size, \n",
    "                        epochs=epochs,\n",
    "                        validation_data=val_generator,\n",
    "                        validation_steps=val_samples_num // batch_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 画图，将训练时的acc和loss都绘制到图上\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "def plot_training(history):\n",
    "    plt.figure(12)\n",
    "    \n",
    "    plt.subplot(121)\n",
    "    train_acc = history.history['acc']\n",
    "    val_acc = history.history['val_acc']\n",
    "    epochs = range(len(train_acc))\n",
    "    plt.plot(epochs, train_acc, 'b',label='train_acc')\n",
    "    plt.plot(epochs, val_acc, 'r',label='test_acc')\n",
    "    plt.title('Train and Test accuracy')\n",
    "    plt.legend()\n",
    "    \n",
    "    plt.subplot(122)\n",
    "    train_loss = history.history['loss']\n",
    "    val_loss = history.history['val_loss']\n",
    "    epochs = range(len(train_loss))\n",
    "    plt.plot(epochs, train_loss, 'b',label='train_loss')\n",
    "    plt.plot(epochs, val_loss, 'r',label='test_loss')\n",
    "    plt.title('Train and Test loss')\n",
    "    plt.legend()\n",
    " \n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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3v1oUqlAKaLzZero1atNFXT77bYCABMCY8bApk1w5pnBjz18GDZsgK5dwz1rM2C1qq4FEJH3gauA5X5t/gmMUNWdAKr6h7ddgSSgCCBAYWBruAMImRtusKf8o4+a51SRIkc3t25tWaj/9S+LLRs7FipXzraRRE8euIezYyLnY86cOXTp0uVoquqrr76ab7/9lvbt25/Qd1paWsCaAjmJmzOEiH8a2ssuu4wxY8awd+9eADZt2sQff/zB5s2bKVasGDfffDN9+/blB0+VkVkKW4C//vqL4sWLU7p0abZu3cpnn30GWArazZs3s3DhQsDSS6elpdGuXTtee+21o7nMc1UddPAg/PJL9P30728PtiefPLatUSN7z0ol9NtvkJYWiWdQRcA/7CrV2+ZPdaC6iHwnIt976iNUdR4wC9jivaarakrGE4hIDxFZJCKLtm3bFu74jlGokAUJrFkDXpU5H2ecYaUoR4+GBQugXj0TBM5zKDi+idySJUtYsmQJq1ev5vbbb6d69eosXryYevXq0b9/fwYOHBhR34EI1HewmgI5iqrmqVfjxo01I8uXLz9hW25www03aJ06dbRv37768ssva926dbVu3bp63nnn6erVq/Xzzz/XevXqaYMGDbRJkya6cOFCVVUdNmyY1qhRQy+66KKgfd92221as2ZNvfzyy7VLly46duxYVVVdsGCBNm/eXOvXr6/NmzfXPXv26OHDh7V3795aq1YtrV+/vr766qshjT9bfsfnnlMtXFh1+/bI+/juO1VQffLJ47fv3ataqJDq449nfvz06Xb811+HdDpgkb1xHWYH8N24twCvqt/1CHwCTMFm+tUwQVEGOAf4FCjhveYBF2qY13ZYHDmi2qqV6mmn2W8TgLVrVVu3tp/jyitVt26N7pSxIi/cw3/++adWrlxZVVWnT5+uzZo10z179qiqampqqm7dulU3bdqkf//9t6qqTpkyRa+66ipVVa1bt66uXbs20/6rVKmi27Zt08WLF2u9evV03759unfvXq1Tp47+8MMPAfves2ePbvX+Sdu3b9eyZcuG9F0C/Z6+6zrcV64/9DO+8rIQiHey5Xfs2NEuo3nzIjv+yBHV885TPf30wA+22rXtaZYZI0bYGDZtUtWjb0HxEwLnYzN43wO/P9Bf/a5H4DWgu9/nr4CmQD/gMb/tA4AHNMxrO2zm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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_training(history_ft)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 用训练好的模型来预测新样本\n",
    "from PIL import Image\n",
    "from keras.preprocessing import image\n",
    "def predict(model, img_path, target_size):\n",
    "    img=Image.open(img_path) # 加载图片\n",
    "    if img.size != target_size:\n",
    "        img = img.resize(target_size)\n",
    "\n",
    "    x = image.img_to_array(img) \n",
    "    x *=1./255 # 相当于ImageDataGenerator(rescale=1. / 255)\n",
    "    x = np.expand_dims(x, axis=0) # 调整图片维度\n",
    "    preds = model.predict(x) # 预测\n",
    "    return preds[0]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.14361556], dtype=float32)"
      ]
     },
     "execution_count": 105,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "predict(model,'E:\\PyProjects\\DataSet\\FireAI\\DeepLearning/FireAI005/cat11.jpg',(IMG_W,IMG_H))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.9942463], dtype=float32)"
      ]
     },
     "execution_count": 106,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "predict(model,'E:\\PyProjects\\DataSet\\FireAI\\DeepLearning//FireAI005/dog4.jpg',(IMG_W,IMG_H))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.7145583], dtype=float32)"
      ]
     },
     "execution_count": 107,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "predict(model,'E:\\PyProjects\\DataSet\\FireAI\\DeepLearning//FireAI005/dog2.jpg',(IMG_W,IMG_H))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 4 images belonging to 2 classes.\n",
      "[[0.14361556]\n",
      " [0.5149474 ]\n",
      " [0.71455824]\n",
      " [0.9942463 ]]\n"
     ]
    }
   ],
   "source": [
    "# 预测一个文件夹中的所有图片\n",
    "new_sample_gen=ImageDataGenerator(rescale=1. / 255)\n",
    "newsample_generator=new_sample_gen.flow_from_directory(\n",
    "        'E:\\PyProjects\\DataSet\\FireAI\\DeepLearning',\n",
    "        target_size=(IMG_W, IMG_H),\n",
    "        batch_size=16,\n",
    "        class_mode=None,\n",
    "        shuffle=False)\n",
    "predicted=model.predict_generator(newsample_generator)\n",
    "print(predicted)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 模型保存\n",
    "# model.save_weights('E:\\PyProjects\\DataSet\\FireAI\\DeepLearning//FireAI005/FireAI005_Model.h5') # 这个只保存weights，不保存模型的结构\n",
    "model.save('E:\\PyProjects\\DataSet\\FireAI\\DeepLearning//FireAI005/FireAI005_Model2.h5') # 对于一个完整的模型，应该要保存这个"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 123,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 模型的加载，预测\n",
    "from keras.models import load_model\n",
    "saved_model=load_model('E:\\PyProjects\\DataSet\\FireAI\\DeepLearning//FireAI005/FireAI005_Model2.h5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 124,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.14361556]\n",
      " [0.5149474 ]\n",
      " [0.71455824]\n",
      " [0.9942463 ]]\n"
     ]
    }
   ],
   "source": [
    "predicted=saved_model.predict_generator(newsample_generator)\n",
    "print(predicted) # saved_model的结果和前面的model结果一致，表面模型正确保存和加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
